// Cross-platform socket wrapper
// Copyright 2004 (C) Ralph Thomas

#include <util/string.h>
#include <util/socket.h>
#include <util/log.h>

#ifdef UNIX
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <poll.h>
#ifndef sockaddr_in
#include <netinet/in.h>
#endif
#endif

#ifdef _WINDOWS
#include <winsock2.h>
#define socklen_t int
#endif

namespace util {
	//
	/// This is an implementation of the socket class using plain BSD
	/// sockets. This implementation uses blocking sockets for
	/// simplicity's sake.
	//
	class socketBsdImpl : public socket {
	  private:
		int _fd;
	  public:
		socketBsdImpl() : _fd( -1 ) {}
		~socketBsdImpl() {
			if( _fd != -1 ) {
#ifdef _WINDOWS
				closesocket( _fd );
				WSACleanup();
#else
				shutdown( _fd, SHUT_RDWR );
				close( _fd );
#endif
			}
		}

		bool connect( util::string hostname, int port ) {
			struct sockaddr_in servaddr;
			struct hostent* host;
#ifdef _WINDOWS
			//
			// WinSock initialization. This was inspired by
			// the Mozilla XPNet socket code.
			//
			int error;
			WORD winSockVersion;
			WSADATA winSockData;

			//
			// Try all of the different versions of winsock which
			// do what we need.
			//
			winSockVersion = MAKEWORD( 2, 2 );
			error = WSAStartup( winSockVersion, &winSockData );
			if( error == WSAVERNOTSUPPORTED ) {
				//
				// Try version 1.1
				//
				winSockVersion = MAKEWORD( 1, 1 );
				error = WSAStartup( winSockVersion, &winSockData );
				if( error == WSAVERNOTSUPPORTED ) return false;
			}
#endif
			_fd = ::socket( AF_INET, SOCK_STREAM, 0 );
			if( _fd == -1 ) return false;

			memset( &servaddr, 0, sizeof( servaddr ) );
			servaddr.sin_family = AF_INET;
			servaddr.sin_port = htons( port );
			
			//XXX: where does this memory come from? do i free it?
			host = gethostbyname( hostname.c_str() );
			if( !host ) return false;

			memcpy( &servaddr.sin_addr, (struct in_addr**) host->h_addr_list[0], sizeof( struct in_addr ) );

			if( ::connect( _fd, (struct sockaddr*) &servaddr, sizeof( servaddr ) ) < 0 ) return false;

			return true;
		}

		virtual errorCode send( util::string message ) {
			char* buf;
			const int bufSize = message.size() + 1;
			int rc = -1;

			if( _fd == -1 ) return kDisconnected;

			buf = new char[ bufSize ];
			for( unsigned int i = 0; i < message.size(); i++ )
				buf[i] = message[i];
			buf[ message.size() ] = 0;

			rc = ::send( _fd, buf, message.size(), 0 );
			delete[] buf;
			if( rc == -1 ) return kFailure;
			return kOk;
		}

		virtual errorCode recieve( util::string& message, int nbytes ) {
			const int bufSize = nbytes + 1;
			char* buf = new char[ bufSize ];
			util::string tmpMsg;
			int rc = -1;

			if( _fd == -1 ) return kDisconnected;
			for( int i = 0; i < bufSize; i++ ) buf[i] = 0;
			rc = recv( _fd, buf, nbytes, 0 );
			if( rc < 1 ) { delete[] buf; return kFailure; }
			message = buf;
			delete[] buf;
			return kOk;
		}

		virtual errorCode recieveLine( util::string& message ) {
			util::string line;
			char buf;

			if( _fd == -1 ) return kDisconnected;
			while( recv( _fd, &buf, 1, 0 ) == 1 ) {
				line += buf;
				if( buf == '\n' ) {
					message = line;
					return kOk;
				}
			}
			return kFailure;
		}

		virtual bool isDataAvailable( int timeout ) {
#ifdef UNIX
			struct pollfd pfd;

			if( _fd == -1 ) return false;

			pfd.fd = _fd;
			pfd.events = POLLIN | POLLPRI;
			pfd.revents = 0;

			int pval = poll( &pfd, 1, timeout * 1000 );
			if( pval <= 0 ) return false;
			return true;
#else
			fd_set socketSet;
			struct timeval socketTime;

			if( _fd == -1 ) return false;

			FD_ZERO( &socketSet );
			FD_SET( _fd, &socketSet );
			socketTime.tv_sec = timeout;
			socketTime.tv_usec = 0;

			int rv = select( _fd+1, &socketSet, NULL, NULL, &socketTime );
			if( (rv == 0) || (rv == -1) ) return false;
			return true;
#endif
		}

		virtual bool startTLS() { return false; }
	};

	socket* socket::fromHostnameAndPort( util::string hostname, int port ) {
		socketBsdImpl* _socket = new socketBsdImpl();
		if( _socket->connect( hostname, port ) ) return _socket;
		delete _socket;
		return NULL;
	}
	
	socket::~socket() {}
};
